library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;

entity mac_to_ip is
      port (
        --// clk, reset
        clk_sys_i: in std_logic;
        rst_n_i: in std_logic;

        --// packet/frame received
        mac_rx_data: in std_logic_vector(15 downto 0);
        mac_rx_data_valid: in std_logic;
            -- one clk-wide pulse indicating a new byte is read from the received frame
            -- and can be read at mac_rx_data
            -- always on from sof to eof (i.e. no gaps)
        mac_rx_sof: in std_logic;
            -- start of frame: one clk-wide pulse indicating the first word in the received frame
            -- aligned with mac_rx_data_valid.
        mac_rx_eof: in std_logic;
            -- end of frame: one clk-wide pulse indicating the last word in the received frame
            -- aligned with mac_rx_data_valid.
        --// received ip frame
        -- excludes mac layer header. includes ip header.
        ip_rx_data: out std_logic_vector(15 downto 0);
        ip_rx_data_valid: out std_logic;
        ip_rx_sof: out std_logic;
        ip_rx_eof: out std_logic;
        ip_byte_count: out std_logic_vector(15 downto 0);
        ip_header_flag: out std_logic;
            -- latency: 2 clks after mac_rx_data
            -- as the ip frame validity is checked on-the-fly, the user should always check if
            -- the ip_rx_data_valid is high at the end of the ip frame (ip_rx_eof) to confirm that the
            -- entire ip frame is valid. validity checks performed within are
            -- (a) correct ip header checksum
            -- ip_byte_count is reset at the start of the data field (i.e. immediately after the header)
            -- always use ip_byte_count using the ip_header_flag context (inside or outside the ip header?)
        --// udp attributes
        rx_udp_cksum: out std_logic_vector(16 downto 0);
            -- udp checksum (including pseudo-header).
            -- correct checksum is either x10000 or x00001
        rx_udp_cksum_rdy: out std_logic
            -- 1 clk pulse. latency: 3 clk after receiving the last udp byte.
        );
end entity;

architecture behavioral of mac_to_ip is
--------------------------------------------------------
--      components
--------------------------------------------------------
--------------------------------------------------------
--     signals
--------------------------------------------------------
-- notations:
-- _e as one-clk early sample
-- _d as one-clk delayed sample
-- _d2 as two-clks delayed sample

--// byte count ----------------------
signal mac_rx_data_valid_d: std_logic := '0';
signal mac_rx_sof_d: std_logic := '0';
signal mac_rx_eof_d: std_logic := '0';
signal mac_rx_data_d: std_logic_vector(15 downto 0) := x"0000";
signal byte_count: integer range 0 to 2047;            -- read/use at mac_rx_data_valid_d

signal rx_type_local: std_logic_vector(3 downto 0) := x"0";
signal rx_type_rdy_local: std_logic := '0';

--// ip byte count ----------------------
signal mac_rx_data_valid_d2: std_logic := '0';
signal mac_rx_sof_d2: std_logic := '0';
signal mac_rx_eof_d2: std_logic := '0';
signal mac_rx_data_d2: std_logic_vector(15 downto 0) := x"0000";
signal ip_byte_count_local: std_logic_vector(15 downto 0);            -- read/use at mac_rx_data_valid_d2
signal ip_byte_count_inc: std_logic_vector(15 downto 0);        -- read/use at mac_rx_data_valid_d2
signal ip_header_flag_local: std_logic := '0';                            -- read/use at mac_rx_data_valid_d2
signal ip_frame_flag: std_logic := '0';                            -- read/use at mac_rx_data_valid_d2
signal ip_rx_sof_local: std_logic := '0';                            -- read/use at mac_rx_data_valid_d2
signal ip_rx_eof_local: std_logic := '0';                            -- read/use at mac_rx_data_valid_d2
signal ip_header_length_words: std_logic_vector(3 downto 0);    -- expressed in 32-bit words. read/use at mac_rx_data_valid_d2
signal ip_rx_data_valid_local: std_logic := '0';
signal ip_byte_count_e: std_logic_vector(15 downto 0) := x"0000";            -- earlier
signal ip_header_flag_e: std_logic := '0';
signal ip_frame_flag_e: std_logic := '0';
signal ip_header_length_words_dec: std_logic_vector(3 downto 0) := "0000";
signal ip_rx_eof_e: std_logic := '0';

--// validate ip header checksum ----------------------
signal ip_header_checksum: std_logic_vector(16 downto 0);    -- 16-bit sum + carry
signal ip_header_checksum_valid: std_logic;                        -- read/use at mac_rx_data_valid_d3
signal ip_header_checksum_valid_rdy: std_logic;                        -- read/use at mac_rx_data_valid_d3
signal ip_header_flag_d: std_logic;

--// ip length ----------------------
signal ip_payload_length: std_logic_vector(15 downto 0) := x"0000";    -- read/use at rx_sample_clk_d3_local. ip payload length in bytes

--// check ip validity ----------------------
signal valid_ip_frame: std_logic := '0';

--//--- udp layer ---------------------------------
signal rx_udp_cksum_local: std_logic_vector(16 downto 0);
signal rx_udp_cksum_a: std_logic_vector(16 downto 0);

------------------------------------------------------
--      implementation
--------------------------------------------------------
begin

---------------------------------------------------
---- packet layer ---------------------------------
---------------------------------------------------

--// packet byte count ----------------------
-- most packet processing is performed with a 1clk latency (processes mac_rx_data_d and mac_rx_data_valid_d)
-- count received bytes for each incoming packet. 0 is the first byte.
byte_count_001: process(clk_sys_i)
begin
if rising_edge(clk_sys_i) then
    if(rst_n_i = '0') then
        byte_count <= 0;
        mac_rx_data_d <= (others => '0');
        mac_rx_data_valid_d <= '0';
        mac_rx_sof_d <= '0';
        mac_rx_eof_d <= '0';
    else
        -- reclock data and sample clock so that they are aligned with the byte count.
        mac_rx_data_valid_d <= mac_rx_data_valid;
        mac_rx_sof_d <= mac_rx_sof;
        mac_rx_eof_d <= mac_rx_eof;

        if(mac_rx_data_valid = '1') then
            mac_rx_data_d <= mac_rx_data;
        end if;

        if(mac_rx_sof = '1') then
            -- just received first byte.
            byte_count <= 0;
        elsif(mac_rx_data_valid = '1') then
            byte_count <= byte_count + 2;
        end if;
    end if;
end if;
end process;

detect_type_001: process(clk_sys_i)
begin
if rising_edge(clk_sys_i) then
    if(rst_n_i = '0') then
        rx_type_local <= x"0";    -- unknown type
        rx_type_rdy_local <= '0';
    else
        if(mac_rx_sof_d = '1') then
            -- clear type to unknown
            rx_type_local <= x"0";    -- unknown type
        elsif(mac_rx_data_valid_d = '1') and (byte_count = 12) and (rx_type_local = 0) then
            -- ethernet encapsulation, rfc 894
            if(mac_rx_data_d = x"0800") then
                -- ip datagram
                rx_type_local <= x"1";
                rx_type_rdy_local <= '1';
            elsif(mac_rx_data_d = x"0806") then
                -- arp request/reply
                rx_type_local <= x"2";
                rx_type_rdy_local <= '1';
            elsif(mac_rx_data_d = x"8035") then
                -- rarp request/reply
                rx_type_local <= x"3";
                rx_type_rdy_local <= '1';
            else
                rx_type_rdy_local <= '0';
              end if;
        elsif(mac_rx_data_valid_d = '1') and (byte_count = 22) and (rx_type_local = 0) then
            -- still unrecognized type at byte count 22, declare unknown type
            rx_type_rdy_local <= '1';
        else
            rx_type_rdy_local <= '0';
        end if;
    end if;
    end if;
end process;

---------------------------------------------------
---- ip layer ---------------------------------
---------------------------------------------------
-- most packet processing is performed with a 2clk latency w.r.t. the input
-- (processes mac_rx_data_d2 and mac_rx_data_valid_d2)
reclock_001: process(clk_sys_i)
begin
if rising_edge(clk_sys_i) then
    if(rst_n_i = '0') then
        mac_rx_data_d2 <= (others => '0');
        mac_rx_data_valid_d2 <= '0';
        mac_rx_sof_d2 <= '0';
        mac_rx_eof_d2 <= '0';
    else
        -- reclock data and sample clock so that they are aligned with the ip word count.
        mac_rx_data_valid_d2 <= mac_rx_data_valid_d;
        mac_rx_sof_d2 <= mac_rx_sof_d;
        mac_rx_eof_d2 <= mac_rx_eof_d;
        mac_rx_data_d2 <= mac_rx_data_d;
    end if;
end if;
end process;

--// ip byte count ----------------------
-- start counting at the first ip header byte. 0 is the first byte.
-- also delineate the ip header boundaries
-- the ip_byte_count, ip_header_flag, ip_header_length_words are to be used at mac_rx_data_valid_d2.
ip_byte_count_001: process(clk_sys_i)
begin
if rising_edge(clk_sys_i) then
    if(rst_n_i = '0') then
        ip_rx_sof_local <= '0';
        ip_header_length_words <= (others => '0');
    else
        if(mac_rx_sof_d = '1') then
            -- clear last ip word count, header flag, ip header length
            ip_rx_sof_local <= '0';
            ip_header_length_words <= (others => '0');
        elsif(mac_rx_data_valid_d = '1') and (byte_count = 14) and (rx_type_local = 1) then
            ip_rx_sof_local <= '1';
            -- capture ip header length in 32-bit words.
            ip_header_length_words <= mac_rx_data_d(11 downto 8);
        elsif (mac_rx_data_valid_d = '1') and (rx_type_local = 1) then
            ip_rx_sof_local <= '0';
        else
            ip_rx_sof_local <= '0';
        end if;
    end if;
end if;
end process;

-- alternate (earlier) computation of the ip_byte_count_e, can be helpful for better timing
ip_header_length_words_dec <= ip_header_length_words - 1;

ip_byte_count_e_001: process(clk_sys_i)
begin
if rising_edge(clk_sys_i) then
    if(rst_n_i = '0') then
        ip_byte_count_e <= (others => '0');
        ip_frame_flag_e <= '0';
        ip_header_flag_e <= '0';
        ip_byte_count_local <= (others => '0');
        ip_byte_count_inc <= conv_std_logic_vector(1, ip_byte_count_inc'length) ;
        ip_frame_flag <= '0';
        ip_header_flag_local <= '0';
        ip_rx_eof_local <= '0';
    else
        -- delay one input byte
        if (mac_rx_data_valid_d2 = '1') then
            ip_byte_count_local <= ip_byte_count_e;
            ip_byte_count_inc <= ip_byte_count_e + 1;
            ip_frame_flag <= ip_frame_flag_e;
            ip_header_flag_local <= ip_header_flag_e;
            ip_rx_eof_local <= ip_rx_eof_e;
        else
            ip_rx_eof_local <= '0';
        end if;

        if(mac_rx_sof_d = '1') then
            -- clear last ip word count
            ip_byte_count_e <= (others => '0');
            ip_frame_flag_e <= '0';
            ip_header_flag_e <= '0';
        elsif(mac_rx_data_valid_d = '1') and (byte_count = 12) and (rx_type_local = 0) and (mac_rx_data_d = x"0800") then
            -- last byte ethernet header
            ip_frame_flag_e <= '1';
            ip_header_flag_e <= '1';
        elsif (ip_rx_eof_e = '1') then
            -- end of ip frame? (could happen before end of mac data because of mac frame padding)
            ip_byte_count_e <= ip_byte_count_e + 2;
            ip_frame_flag_e <= '0';
        elsif (mac_rx_data_valid_d = '1') and (rx_type_local = 1) then
            if (ip_header_flag_e = '1') and (ip_byte_count_e = ("0000000000" & ip_header_length_words_dec & "10")) then
                -- last byte in the ip header
                ip_header_flag_e <= '0';
                -- reset ip_byte_count_e
                ip_byte_count_e <= (others => '0');
            else
                ip_byte_count_e <= ip_byte_count_e + 2;
            end if;
        elsif(mac_rx_eof = '1') then
            -- catch all (in case length field is erroneous)
            ip_header_flag_e <= '0';
        else
            ip_frame_flag_e <= '0';
        end if;
    end if;
end if;
end process;

ip_rx_eof_e <= '1' when (mac_rx_data_valid_d2 = '1') and (ip_header_flag_e = '0') and ((ip_byte_count_e+2) = ip_payload_length) else '0';

--// validate ip header checksum ----------------------
-- perform 1's complement sum of all 16-bit words within the header.
-- ip valid flag ready one clk after the last header word (rx_sample_clk_d2_local)
-- this applies only to ipv4 (no such field in ipv6)
ip_header_checksum_001: process(clk_sys_i)
begin
if rising_edge(clk_sys_i) then
    if(rst_n_i = '0') then
        ip_header_checksum <= (others => '0');
        ip_header_checksum_valid <= '0';
        ip_header_checksum_valid_rdy <= '0';
        ip_header_flag_d <= '0';
    else
        ip_header_flag_d <= ip_header_flag_local;

        if(mac_rx_sof_d2 = '1') then
            -- clear last checksum
            ip_header_checksum <= (others => '0');
            ip_header_checksum_valid <= '0';
            ip_header_checksum_valid_rdy <= '0';
        elsif (mac_rx_data_valid_d2 = '1') and (ip_header_flag_local = '1')   then
            -- once every word (2 bytes) do 16-bit sum, 1's complement. if previous carry in ip_header_checksum(16), add 1.
            ip_header_checksum <=  ("0" & ip_header_checksum(15 downto 0))
                + ("0" & mac_rx_data_d2) + (x"0000" & ip_header_checksum(16));
        elsif (ip_header_flag_local = '0') and (ip_header_flag_d = '1') then
            -- end of ip header. correct for last carry and verify checksum.
            ip_header_checksum_valid_rdy <= '1';
            if(ip_header_checksum(16 downto 0) = "0" & x"ffff") then
                -- case1: no previous carry. checksum ok
                ip_header_checksum_valid <= '1';
            elsif(ip_header_checksum(16 downto 0) = "1" & x"fffe") then
                -- case2: previous carry. checksum ok
                ip_header_checksum_valid <= '1';
            end if;
        else
            ip_header_checksum_valid_rdy <= '0';
        end if;
    end if;
end if;
end process;

--// ip length ----------------------
-- parse ip payload length (excluding header), expressed in bytes.
ip_payload_length_001: process(clk_sys_i)
begin
if rising_edge(clk_sys_i) then
    if(rst_n_i = '0') then
        ip_payload_length <= (others => '0');
    else
        if(mac_rx_sof_d2 = '1') then
            -- clear length
            ip_payload_length <= (others => '0');
        elsif (mac_rx_data_valid_d2 = '1') and (ip_header_flag_local = '1') and (ip_byte_count_local = 2) then
            ip_payload_length <= mac_rx_data_d2 - ("0000000000" & ip_header_length_words & "00");
        end if;
    end if;
end if;
end process;

--// check ip validity ----------------------
ip_byte_count <= ip_byte_count_local;
ip_rx_data <= mac_rx_data_d2;
ip_rx_sof <= ip_rx_sof_local;
ip_rx_eof <= ip_rx_eof_local;
ip_rx_data_valid_local <= mac_rx_data_valid_d2 and valid_ip_frame and ip_frame_flag;
ip_rx_data_valid <= ip_rx_data_valid_local;
ip_header_flag <= ip_header_flag_local;

-- the received ip frame is presumed valid until proven otherwise.
-- ip frame validity checks include:
-- (a) correct ip header checksum
valid_ip_frame_check_001: process(clk_sys_i)
begin
if rising_edge(clk_sys_i) then
    if(rst_n_i = '0') then
        valid_ip_frame <= '1';
    else
        if(mac_rx_sof = '1') then
            -- just received first byte. valid until proven otherwise
            valid_ip_frame <= '1';
        elsif(rx_type_rdy_local = '1') and (rx_type_local /= 1) then
            -- (a) the received packet type is not an ip datagram
            valid_ip_frame <= '0';
        elsif(ip_header_checksum_valid_rdy = '1') and (ip_header_checksum_valid = '0') then
            -- (c) invalid ip header checksum
            valid_ip_frame <= '0';
        end if;
    end if;
end if;
end process;

---------------------------------------------------
--//--- udp layer ---------------------------------
---------------------------------------------------
-- compute udp checksum, using the pseudo-header.
udp_checksum_001: process(clk_sys_i)
begin
if rising_edge(clk_sys_i) then
    rx_udp_cksum_rdy <= ip_rx_eof_local;
    if (mac_rx_data_valid_d2 = '1')  and (ip_frame_flag = '1') then
        -- ipv4
        if(ip_header_flag_local = '1') then
        -- pseudo header
            if(ip_byte_count_local = 2)  then
                rx_udp_cksum_local <= rx_udp_cksum_a;
            elsif (ip_byte_count_local >= 8) and (ip_byte_count_local <= 18)
                and (ip_byte_count_local /= 10) then
                rx_udp_cksum_local <= ("0" & rx_udp_cksum_local(15 downto 0)) + rx_udp_cksum_a + rx_udp_cksum_local(16);
            end if;
        else
            -- entire udp packet (ip frame - header), 16-bit at a time
                rx_udp_cksum_local <= ("0" & rx_udp_cksum_local(15 downto 0)) + rx_udp_cksum_a + rx_udp_cksum_local(16);
        end if;
    end if;
end if;
end process;

udp_checksum_002: process(clk_sys_i)
begin
if (ip_header_flag_local = '1') then
    -- pseudo-header
    if(ip_byte_count_local = 2) then
        rx_udp_cksum_a <= "0" & mac_rx_data_d2 - 20;    -- udp length
    elsif(ip_byte_count_local = 8) then
        rx_udp_cksum_a <= "0" & x"00" & mac_rx_data_d2(7 downto 0);    -- zeros + protocol (0x11)
    else
        rx_udp_cksum_a <= "0" & mac_rx_data_d2; -- ip source and destination addresses
    end if;
else
    -- entire udp packet (ip frame - header), 16-bit at a time
    rx_udp_cksum_a <= "0" & mac_rx_data_d2; -- add 16-bit at a time
end if;
end process;

rx_udp_cksum <= not rx_udp_cksum_local(16 downto 0);

end behavioral;
